/*
 * MomberBanMidlet.java
 * 
 * author: The MomberBan Team
 * date: 12.09.2008
 * 
 */

package at.momberban.game.me;

import java.util.Enumeration;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/**
 * @author patchinsky
 */
public class MomberBanMidlet extends MIDlet implements CommandListener {

    private static final String MAIN_CAPTION = "MomberBan";

    private static final String SET_MAX_PLAYERS_CAPTION = "Set max. Players";

    private static final String SELECT_MAP_CAPTION = "Select Map";

    private static final String CREATE_GAME_CAPTION = "Create Game";

    private static final String JOIN_GAME_CAPTION = "Join Game";

    private static final String EXIT_GAME_CAPTION = "Exit";

    private static final String START_GAME_CAPTION = "Start Game";

    private static final String NEXT_GAME_CAPTION = "Again!!";

    private static final String BACK_CAPTION = "Back";

    private static final String REFRESH_CAPTION = "Refresh";

    private static final String SOUND_ON_CAPTION = "Sounds: on";

    private static final String SOUND_OFF_CAPTION = "Sounds: off";

    private static final String MP3_NOT_SUPPORTED_CAPTION = "Device does not support MP3";

    private Game game;

    private GameMap[] maps;

    private List mainList;

    private List menuList;

    private List highScore;

    private Command setMaxPlayersCommand;

    private Command selectMapCommand;

    private Command startGameCommand;

    private Command exitCommand;

    private Command backCommand;

    private Command againCommmand;

    private Command refreshCommand;

    private NetworkManager networkManager;

    private boolean isServer;

    private String[] servers;

    private boolean notFirstTime;

    /**
     * listen for new players
     */
    private GameListener pregameListener = new GameListener(this);

    /**
     * 
     */
    public MomberBanMidlet() {
        // Logger.setLogLevel(Logger.RELEASE);

        mainList = new List(MAIN_CAPTION, List.IMPLICIT);

        exitCommand = new Command(EXIT_GAME_CAPTION, Command.EXIT, 0);
        backCommand = new Command(BACK_CAPTION, Command.BACK, 0);
        againCommmand = new Command(NEXT_GAME_CAPTION, Command.ITEM, 0);

        mainList.append(CREATE_GAME_CAPTION, null);
        mainList.append(JOIN_GAME_CAPTION, null);
        mainList.append(SOUND_ON_CAPTION, null);
        mainList.addCommand(exitCommand);
        mainList.setCommandListener(this);
        menuList = new List("Menu List", List.IMPLICIT);

        highScore = new List("Highscore", List.IMPLICIT);
        highScore.addCommand(againCommmand);
        highScore.addCommand(exitCommand);
        highScore.setCommandListener(this);
    }

    private void removeAllMainListCommands() {
        mainList.removeCommand(setMaxPlayersCommand);
        mainList.removeCommand(selectMapCommand);
        mainList.removeCommand(startGameCommand);
        mainList.removeCommand(exitCommand);
        mainList.removeCommand(refreshCommand);
        mainList.removeCommand(backCommand);
        mainList.removeCommand(againCommmand);
    }

    /**
     * override
     * 
     * @see javax.microedition.midlet.MIDlet#destroyApp(boolean)
     */
    protected void destroyApp(boolean unconditional)
            throws MIDletStateChangeException {
        // not used
        terminate(unconditional);
    }

    private void terminate(boolean force) {
        Logger.log("destroy called. force: " + force);

        if (this.pregameListener != null) {
            this.pregameListener.disconnect();
            Logger.log("all disconnected");
        }

        if (!force) {
            mainList.deleteAll();
            mainList.setTitle(MAIN_CAPTION);

            removeAllMainListCommands();
            mainList.append(CREATE_GAME_CAPTION, null);
            mainList.append(JOIN_GAME_CAPTION, null);
            mainList.append(SOUND_ON_CAPTION, null);

            mainList.addCommand(exitCommand);
            mainList.setCommandListener(this);
            notFirstTime = false;
            showMainScreen();
            // startApp();
            // throw new MIDletStateChangeException();
        } else {
            notifyDestroyed();
        }
    }

    /**
     * override
     * 
     * @see javax.microedition.midlet.MIDlet#pauseApp()
     */
    protected void pauseApp() {
        Media.mute();
    }

    /**
     * override
     * 
     * @see javax.microedition.midlet.MIDlet#startApp()
     */
    protected void startApp() throws MIDletStateChangeException {
        showMainScreen();
    }

    /**
     * display the main screen
     */
    private void showMainScreen() {
        if (BTServerNetworkManager.supportsBluetooth()) {
            if (!notFirstTime) {
                if (Media.hasMP3Support()) {
                    Media.turnSoundOn();
                } else {
                    mainList.set(2, MP3_NOT_SUPPORTED_CAPTION, null);
                    Media.turnSoundOff();
                }
                Display.getDisplay(this).setCurrent(mainList);
                notFirstTime = true;
            } else {
                Media.unmute();
            }
        } else {
            Alert alert = new Alert(
                    "Error",
                    "Bluetooth is not available. Please check, if it is switched on.",
                    null, AlertType.ERROR);
            alert.setTimeout(Alert.FOREVER);
            alert.setCommandListener(this);
            Display.getDisplay(this).setCurrent(alert);
        }
    }

    /**
     * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
     *      javax.microedition.lcdui.Displayable)
     */
    public void commandAction(Command cmd, Displayable disp) {
        if (cmd == List.SELECT_COMMAND) {
            if (disp == mainList) {
                String command = mainList
                        .getString(mainList.getSelectedIndex());
                if ((command.equals(CREATE_GAME_CAPTION))
                        || (command.equals(JOIN_GAME_CAPTION))) {
                    maps = GameMap.getMaps();
                    game = new Game();
                    game.setMap(maps[0]);

                    if (command.equals(CREATE_GAME_CAPTION)) {
                        // Game acts as Server
                        mainList.setTitle(CREATE_GAME_CAPTION);
                        isServer = true;
                        networkManager = BTServerNetworkManager.getManager();
                    } else if (command.equals(JOIN_GAME_CAPTION)) {
                        // Game acts as Client
                        isServer = false;
                        networkManager = BTClientNetworkManager.getManager();
                    }

                    Logger.setIsHost(isServer);

                    game.setHost(isServer);
                    game.setNetworkManager(networkManager);

                    if (isServer) {
                        // initialize Game and show Pre-Game Menu
                        mainList.deleteAll();
                        selectMapCommand = new Command(SELECT_MAP_CAPTION,
                                Command.ITEM, 0);
                        mainList.addCommand(selectMapCommand);

                        setMaxPlayersCommand = new Command(SET_MAX_PLAYERS_CAPTION,
                                Command.ITEM, 0);
                        mainList.addCommand(setMaxPlayersCommand);

                        startGameCommand = new Command(START_GAME_CAPTION,
                                Command.ITEM, 0);
                        mainList.addCommand(startGameCommand);
                        synchronized (this) {
                            // Tell main Thread to not connect but to listen
                            notifyAll();
                        }
                    } else {
                        // Show list of availabe services
                        discover(false);
                        refreshCommand = new Command(REFRESH_CAPTION, Command.OK, 0);
                        mainList.addCommand(refreshCommand);
                    }

                    game.prepare();

                    // listen for events
                    new Thread(this.pregameListener).start();

                } else if (command.equals(SOUND_ON_CAPTION)) {
                    Media.turnSoundOff();
                    mainList.set(2, SOUND_OFF_CAPTION, null);
                } else if (command.equals(SOUND_OFF_CAPTION)) {
                    if (Media.hasMP3Support()) {
                        mainList.set(2, SOUND_ON_CAPTION, null);
                        Media.turnSoundOn();
                    } else {
                        mainList.set(2, MP3_NOT_SUPPORTED_CAPTION, null);
                        Media.turnSoundOff();
                    }
                } else if (command.equals(MP3_NOT_SUPPORTED_CAPTION)) {
                    // do nothing
                } else {
                    // Server selected => connect in main thread
                    this.pregameListener.connectToService(mainList
                            .getSelectedIndex());
                }
            } else if (disp == menuList) {
                // Either setMaxPlayers or selectMap
                if (menuList.getTitle().equals(SET_MAX_PLAYERS_CAPTION)) {
                    setMaxPlayers(Integer.parseInt(menuList.getString(menuList
                            .getSelectedIndex())));
                    Display.getDisplay(this).setCurrent(mainList);
                } else if (menuList.getTitle().equals(SELECT_MAP_CAPTION)) {
                    game.setMap(maps[menuList.getSelectedIndex()]);
                    Display.getDisplay(this).setCurrent(mainList);
                }
                menuList.removeCommand(backCommand);
                menuList.deleteAll();
            }
        } else if (cmd == setMaxPlayersCommand) {
            if (isServer) {
                // show setMaxPlayers menu
                menuList.setTitle(SET_MAX_PLAYERS_CAPTION);
                for (int i = 0; i < game.getMap().getDefaultMaxPlayers(); i++) {
                    menuList.append(String.valueOf(i + 1), null);
                }
                menuList.addCommand(backCommand);
                menuList.setCommandListener(this);
                Display.getDisplay(this).setCurrent(menuList);
            }
        } else if (cmd == selectMapCommand) {
            if (isServer) {
                // show selectMap menu
                menuList.setTitle(SELECT_MAP_CAPTION);
                for (int i = 0; i < maps.length; i++) {
                    menuList.append(maps[i].getTitle(), null);
                }
                menuList.addCommand(backCommand);
                menuList.setCommandListener(this);
                Display.getDisplay(this).setCurrent(menuList);
            }
        } else if (cmd.equals(refreshCommand)) {
            discover(true);
        } else if (cmd.equals(againCommmand)) {
            // game.prepare();

            // listen for events
            // new Thread(this.pregameListener).start();

            if (isServer) {
                game.start();
            }
        } else if (cmd == startGameCommand) {
            if (isServer) {
                // start Game
                game.start();

                Display.getDisplay(this).setCurrent(game.getCanvas());
                synchronized (this) {
                    notifyAll();
                }
            }
        } else if ((cmd == exitCommand) || (cmd == Alert.DISMISS_COMMAND)) {
            boolean force = false;
            if (mainList.getTitle().equals(MAIN_CAPTION)) {
                force = true;
            }
            terminate(force);
        } else if (cmd == backCommand) {
            Display.getDisplay(this).setCurrent(mainList);
            menuList.deleteAll();
        }
    }

    /**
     * @param number
     */
    private void setMaxPlayers(int number) {
        game.getMap().setMaxPlayers(number);
    }

    /**
     * @param noCache
     *            if the NetworkManager shall use the cache or not
     */
    private void discover(boolean noCache) {
        mainList.deleteAll();
        mainList.setTitle("Discovering...");
        servers = game.discover(noCache);
        if (servers != null) {
            mainList.deleteAll();
            mainList.setTitle(JOIN_GAME_CAPTION);
            for (int i = 0; (i < servers.length) && (servers[i] != null); i++) {
                mainList.append(servers[i], null);
            }
        } else {
            Alert alert = new Alert("Error", "No Services found", null,
                    AlertType.ERROR);
            alert.setTimeout(Alert.FOREVER);
            Display.getDisplay(this).setCurrent(alert);
            alert.setCommandListener(this);
        }
    }

    /**
     * return the mainList
     * 
     * @return
     */
    public List getMainList() {
        return mainList;
    }

    /**
     * return the refresh
     * 
     * @return
     */
    public Command getRefresh() {
        return refreshCommand;
    }

    /**
     * @return the game
     */
    public Game getGame() {
        return game;
    }

    /**
     * @return the isServer
     */
    public boolean isServer() {
        return isServer;
    }

    public void setCurrent(Displayable disp) {
        if (disp instanceof Alert) {
            disp.setCommandListener(this);
        }
        Display.getDisplay(this).setCurrent(disp);
    }
    /**
     * listen to the network
     */
    private class GameListener implements Runnable, EventListener {

        private int serviceToConnectTo = -1;

        private boolean isListening = false;

        private boolean loggedIn;

        private MomberBanMidlet midlet;
        
        private boolean again;

        /**
         * @param midlet
         */
        public GameListener(MomberBanMidlet midlet) {
            this.midlet = midlet;
        }

        /**
         * updates the list when a player connects
         */
        private synchronized void waitForGameStart() {
            while (!game.isStarted() && this.again) {
                Logger.log("game NOT ed. waiting for players");
                try {
                    if (!game.isFinished()) {
                        getMainList().deleteAll();
                        Enumeration i2 = game.getPlayers().keys();
                        while (i2.hasMoreElements()) {
                            Integer key = (Integer) i2.nextElement();
                            Player p = (Player) game.getPlayers().get(key);
                            getMainList().append(p.getName(),
                                    Player.getFrontViewImage(key.intValue()));
                        }
                    }

                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * waiting for a service connect (client)
         */
        private synchronized void waitForService() {
            while (this.serviceToConnectTo == -1) {
                try {
                    Logger.log("no service to connect yet");
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * wait for game end
         */
        private synchronized void waitForGameEnd() {
            while (!game.isFinished() && this.again) {
                try {
                    Logger.log("wait for game end");
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public void run() {
            this.again = true;
            
            if (game == null) {
                throw new RuntimeException("game must not be null");
            }

            // registering (network, game)
            // game.getEventGenerator().register(this);
            game.register(this);

            Logger.log("midlet-event-listener started & registered");
            if (!loggedIn && !isServer) {
                waitForService();
                try {
                    login();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            if (!isListening && isServer) {
                Logger.log("listen");
                game.listen();
                getMainList().append(game.getPlayer().getName(),
                        Player.getFrontViewImage(1));

                this.isListening = true;
                this.loggedIn = true; // a server is always logged in
            }

            while (this.again) {
                waitForGameStart();
                if (!this.again) {
                    break;
                }

                // unregister midlet event listener
                Display.getDisplay(midlet).setCurrent(game.getCanvas());

                // game started
                waitForGameEnd();
                if (!this.again) {
                    break;
                }

                // statistics
                Logger.log("now i know, my master");

                highScore.deleteAll();
                Player winner = game.getWinner();

                Enumeration i2 = game.getPlayers().keys();
                while (i2.hasMoreElements()) {
                    Integer key = (Integer) i2.nextElement();
                    Player p = (Player) game.getPlayers().get(key);
                    highScore.append(p.getName() + " F:" + p.getFrags() + " D:"
                            + p.getDeaths(), null);
                }

                if (winner == null) {
                    highScore.setTitle("!!! Draw Game !!!");
                } else {
                    if (game.getPlayer() == winner) {
                        highScore.setTitle("!!! YOU WIN !!!");
                    } else {
                        highScore.setTitle(winner.getName() + " wins !");
                    }
                }
                Display.getDisplay(this.midlet).setCurrent(highScore);

                game.prepare();
            }

            Logger.log("midlet-event-listener terminated");
        }

        /**
         * try to login
         */
        private synchronized final void login() {
            getMainList().setTitle("Connecting...");
            int loginTry = 1;
            do {
                this.loggedIn = game.connect(serviceToConnectTo);
                Display.getDisplay(this.midlet).getCurrent().setTitle(
                        "Wait: #" + loginTry);
                loginTry++;
                try {
                    Logger.log("Wait: #" + loginTry);
                    // wait();
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } while (loginTry <= 10 && !this.loggedIn);

            if (this.loggedIn) {
                getMainList().deleteAll();
                getMainList().removeCommand(getRefresh());

                Display.getDisplay(midlet).getCurrent().setTitle(
                        "Connected and logged in");
            } else { // connect failed
                Alert alert = new Alert("Error", "Unable to connect", null, AlertType.ALARM);
                alert.setTimeout(Alert.FOREVER);
                setCurrent(alert);
            }
        }

        /**
         * @param serviceId
         */
        public synchronized void connectToService(int serviceId) {
            Logger.log("connect to services: " + serviceId);
            this.serviceToConnectTo = serviceId;
            this.notifyAll();
        }

        /**
         * stop again
         */
        public synchronized void stopAgain() {
            this.again = false;
            this.notifyAll();
        }

        /**
         * @see at.momberban.game.me.EventListener#wakeUp()
         */
        public synchronized void wakeUp() {
            Logger.log("midlet thread awake");
            if (game.isTerminated()) {
                terminate(false);
            } else {
                this.notifyAll();
            }
        }

        /**
         * disconnect everything
         */
        public synchronized void disconnect() {
            isServer = false;
            
            this.isListening = false;
            this.loggedIn = false;
            this.again = false;
            this.serviceToConnectTo = -1;
            if (game != null) {
                game.disconnect();
            }
            this.notifyAll();
        }
    }
}
